/*
 * g++ -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=0 -fsigned-char -DSTREAMPLAN -DPGXC -march=armv8-a+crc+lse -O2 -g3  -D__USE_NUMA  -Wall -Wpointer-arith -Wno-write-strings -fnon-call-exceptions -fno-common -freg-struct-return -pipe -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -DENABLE_GSTRACE -fno-aggressive-loop-optimizations -Wno-attributes -fno-omit-frame-pointer -fno-expensive-optimizations -Wno-unused-but-set-variable -fstack-protector -Wl,-z,relro,-z,now -Wl,-z,noexecstack -std=c++14 -pthread  -D_REENTRANT -D_THREAD_SAFE -D_POSIX_PTHREAD_SEMANTICS cas_performance_test.cpp -o castest
 *
 */

#include <iostream>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <sstream>
#include <pthread.h>


using namespace std;

#define MAXDOP 2000

typedef unsigned int uint32;

struct Param {
    int myid;
    int sendto;
    int excepted_count;
};

int s_time[MAXDOP];
int e_time[MAXDOP];
unsigned long trycounts[MAXDOP];

volatile bool ready;
volatile uint32 ball;


inline int gettime() {
    struct timeval tv;
    gettimeofday(&tv, NULL);

    return tv.tv_sec;
}

static __inline__ uint32 cmp_and_swap(volatile uint32* ptr, uint32 excepted, uint32 newval) {
    uint32 prev;
    uint32 temp;

    __asm__ __volatile__ (
    "0: \n\t"
    "ldaxr %w[prev], %[ptr] \n\t"
    "cmp %w[prev], %w[excepted] \n\t"
    "bne 1f \n\t"
    "stlxr %w[temp], %w[newval], %[ptr] \n\t"
    "cbnz %w[temp], 0b \n\t"
    "1: \n\t"
    : [prev]"=&r" (prev),
    [temp]"=&r" (temp),
    [ptr]"+Q" (*ptr)
    : [excepted]"IJr" (excepted),
    [newval]"r" (newval)
    : "cc", "memory"
    );
    return prev;
}

void* job(void* args) {
    uint32 myid = (uint32)((struct Param*)args)->myid;
    uint32 sendto = (uint32)((struct Param*)args)->sendto;
    int excepted_count = ((struct Param*)args)->excepted_count;
    uint32 oldval;

    /* wait ready */
    while (!ready);

    s_time[myid] = gettime();
    for (int curr_count = 0; curr_count < excepted_count; curr_count ++) {

        for (;;) {
            // oldval = __sync_val_compare_and_swap(&ball, myid, sendto);
            oldval = cmp_and_swap(&ball, myid, sendto);

            if (oldval == myid) {
                break;
            } else {
                trycounts[myid] ++;
            }
        }
    }
    e_time[myid] = gettime();

    return NULL;
}

int main(int argc, char *argv[]) {
    int dop = argc > 1 ? atoi(argv[1]) : 50;
    int excepted_count = argc > 2 ? atoi(argv[2]) : 100;
    cout << "dop=" << dop << "  excepted_count=" << excepted_count << endl;

    struct Param params[MAXDOP];
    pthread_t tids[MAXDOP];
    ready = false;

    // create thread
    for (int i = 0; i < dop; i++) {
        params[i].myid = i;
        params[i].sendto = (i + 1) % dop;
        params[i].excepted_count = excepted_count;

        pthread_create(&tids[i], NULL, job, &params[i]);
    }

    // thread job run
    int smaintime = gettime();
    ready = true;
    for (int i = 0; i < dop; i++) {
        pthread_join(tids[i], NULL);
    }
    int emaintime = gettime();

    // print results
    unsigned long all_trycounts = 0;
    unsigned long all_time = 0;
    for (int i = 0; i < dop; i++) {
        all_time += (e_time[i] - s_time[i]);
        all_trycounts += trycounts[i];
        cout << "thread" << i << "  usetime(s):" << e_time[i] - s_time[i] << " trycounts:" << trycounts[i] << endl;
    }
    cout << "dop=" << dop << "  excepted_count=" << excepted_count << endl;
    cout << "all time(s):" << emaintime - smaintime << endl;
    cout << "avg time(s):" << all_trycounts / dop << endl;
    cout << "avg trycounts:" << all_time / dop << endl;

    return 0;
}



